#include "texture.h"

#include <png.h>
#include <cstdio>
#include <iostream>

namespace griselda
{
    namespace graphics
    {
        /**
         * Loads a texture (png format!) into an OpenGL texture
         * object using libpng and OpenGL. Width and height of the
         * texture should be powers of two, e.g. 256x128. RGB only!
         *
         * @param  filename path to png file
         * @return texture or 0 if loading failed
         */
        GLuint load_texture_png(const std::string& filename)
        {
            // header for format test
            png_byte header[8];

            // open file as binary
            FILE* fp = fopen(filename.c_str(), "rb");
            if(!fp)
            {
                // cannot open
                return 0;
            }

            // read header
            fread(header, 1, 8, fp);

            // test format
            int is_png = !png_sig_cmp(header, 0, 8);
            if(!is_png)
            {
                // no png
                fclose(fp);
                return 0;
            }

            // create png struct
            png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                                         0, 0, 0);
            if(!png_ptr)
            {
                fclose(fp);
                return 0;
            }

            // create png info struct
            png_infop info_ptr = png_create_info_struct(png_ptr);
            if(!info_ptr)
            {
                png_destroy_read_struct(&png_ptr, 0, 0);
                fclose(fp);
                return 0;
            }

            // create png end info struct
            png_infop end_info = png_create_info_struct(png_ptr);
            if(!end_info)
            {
                png_destroy_read_struct(&png_ptr, &info_ptr, 0);
                fclose(fp);
                return 0;
            }

            if(setjmp(png_jmpbuf(png_ptr)))
            {
                png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
                fclose(fp);
                return 0;
            }

            // init reading
            png_init_io(png_ptr, fp);

            // already read header
            png_set_sig_bytes(png_ptr, 8);

            // read info up to image data
            png_read_info(png_ptr, info_ptr);

            // info variables
            int bit_depth;
            int color_type;
            png_uint_32 twidth, theight;

            // get png info
            png_get_IHDR(png_ptr, info_ptr, &twidth, &theight, &bit_depth,
                         &color_type, 0, 0, 0);

            // could set width and height here (out vars)

            // update info struct
            png_read_update_info(png_ptr, info_ptr);

            // size in bytes
            int row_bytes = png_get_rowbytes(png_ptr, info_ptr);

            // allocate image_data for OpenGL
            png_byte* image_data = new png_byte[row_bytes * theight];
            if(!image_data)
            {
                png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
                fclose(fp);
                return 0;
            }

            // create row pointer for reading
            png_bytep* row_pointers = new png_bytep[theight];
            if(!row_pointers)
            {
                png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
                delete[] image_data;
                fclose(fp);
                return 0;
            }

            // set row_pointers to correct offset
            for(int i = 0; i < theight; ++i)
            {
                row_pointers[theight - 1 - i] = image_data + i * row_bytes;
            }

            // read png to image_data through row_pointers
            png_read_image(png_ptr, row_pointers);

            // generate OpenGL texture
            GLuint texture;
            glGenTextures(1, &texture);
            glBindTexture(GL_TEXTURE_2D, texture);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, twidth, theight, 0,
                         GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*) image_data);

            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

            // clean up
            png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
            delete[] image_data;
            delete[] row_pointers;
            fclose(fp);

            return texture;
        }
    }
}

